<template>
  <div ref="wordCloudBox" class="word-cloud-box" :class="myClass" :style="getBoxStyle()">
    <div ref="wordCloudBoxInner" class="word-cloud-box-inner">
      <div class="word-cloud-title">{{ title }}</div>
      <div v-if="type === 'cloud' || wordTypes.indexOf(type) === -1" id="tagscloud" ref="wordCloud" class="word-cloud" :style="'width: 100%; height: ' + height + 'px;'">
        <p
          v-for="(item, index) in clouds"
          :key="index"
          :style="styleArray[index]"
          @mouseover="mouseoverEvent($event, index)"
          @mouseout="mouseoutEvent($event, index)"
          @click="doSearch(item.value || item.name || item)"
        >
          <!-- :class="mcList[index] && mcList[index].color" -->
          <span>{{ item.name || item }}</span>
        <!-- ：<span>{{ item.value }}</span> -->
        </p>
      </div>
      <ul v-if="type === 'dotList'" id="dotList" ref="worddotList" class="word-dot-list" :style="'width: 100%; height: ' + height + 'px;'">
        <li v-for="(item, index) in clouds" :key="index" :style="liStyle" @click="doSearch(item.value || item.name || item)">
          <span class="dot" /> {{ item.name || item }}
        </li>
      </ul>
      <ul v-if="type === 'numList'" id="numList" ref="wordnumList" class="word-num-list" :style="'width: 100%; height: ' + height + 'px;'">
        <li v-for="(item, index) in clouds" :key="index" :style="liStyle" @click="doSearch(item.value || item.name || item)">
          <span class="num bg-blue">{{ index + 1 }}</span>{{ item.name || item }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'WordCloud',
  props: {
    type: {
      type: String,
      default: 'cloud'
    },
    clouds: {
      type: Array,
      default: []
    },
    width: {
      type: Number,
      default: 300
    },
    height: {
      type: Number,
      default: 300
    },
    title: {
      type: String,
      default: '词云'
    },
    wordLink: {
      type: String,
      default: ''
    },
    wordLinkTarget: {
      type: String,
      default: '_blank'
    },
    rows: {
      type: Number,
      default: 1
    },
    myClass: {
      type: String,
      default: ''
    },
    myStyle: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      liStyle: 'width: 100%;',
      rowWidth: 150, // 列表展示模式下 列宽的最小宽度
      overIndex: -1,
      wordMinSize: 16, // 词云云标签最小字号
      wordType: this.type,
      wordTypes: ['cloud', 'dotList', 'numList'], // 组件允许注册类型 cloud：词云 list：普通列表 list2：含有数字的列表
      // colors: ['blue', 'yellow', 'green'], // 词云云标签可用字体颜色
      colors: ['#427DC9', '#56A08F', '#E77F83', '#36428E', '#E49361', '#455097'], // 词云云标签可用字体颜色
      mcList: [], // 存放每个云标签的可视宽度、高度（因vue数据驱动原因，故必须在dom生成之后获取）
      option: {
        // 计算所需数值，没太搞明白
        radius: 80, // 设置同时能显示的云标签数 数值越小显示越密集  80-100 是个不错的区间
        d: 100,
        lasta: 1,
        lastb: 1,
        distr: true,
        tspeed: 15, // 设置词云云标签动态浮动速度 10-20 是个不错的选择
        size: 100,
        mouseX: 0,
        mouseY: 10,
        howElliptical: 1
      },
      triFunc: {},
      timer: null,
      styleArray: [] // 存放每个云标签的css样式
    }
  },
  watch: {
    clouds() {
      // 子组件从父组件中获取值之后不会实时更新，可通过watch监听，做手动异步刷新
      if (this.timer) {
        clearInterval(this.timer)
        this.timer = null
      }

      this.$nextTick(() => {
        this.init()
      })
    }
  },
  mounted() {
    this.init()
  },
  beforeDestroy() {
    // 组件销毁，清除定时器（什么时候也不对自己再解释）
    if (this.timer) {
      clearTimeout(this.timer)
      this.timer = null
    }
  },
  methods: {
    doSearch(value) {
      const tg = this.wordLinkTarget
      const _that = this
      if (this.wordLink !== '') {
        const { href } = this.$router.resolve({
          path: `${_that.wordLink}`,
          query: {
            type: _that.$store.getters.searchData.type,
            text: encodeURI(value)
          }
        })
        if (tg !== '_self' && tg !== '_blank') {
          this.wordLinkTarget = '_blank'
        }
        window.open(href, this.wordLinkTarget)
      }
    },
    mouseoverEvent(e, index) {
      // 悬浮操作，未做处理（因对计算公式没研究透）
      this.overIndex = index
    },
    mouseoutEvent(e, index) {
      // 离开操作，同上
      this.overIndex = -1
    },
    setTimer() {
      this.timer = setInterval(() => {
        this.update()
      }, 100)
    },
    init() {
      this.$refs.wordCloudBoxInner.style.width = (this.$refs.wordCloudBox.offsetWidth - 10) + 'px'
      if (this.$data.wordTypes.indexOf(this.type) === -1 || this.type === 'cloud') {
        this.mcList = [] // 因原插件基于dom操作，所以此处选择清除一下这个（其他部分因它而生，不及时清空，页面帅不过10s）
        for (let i = 0; i < this.clouds.length; i++) {
          this.mcList.push({
            offsetWidth: this.$refs['wordCloud'].children[i].offsetWidth,
            name: this.clouds[i] || this.clouds[i].name,
            color: this.colors[i % this.colors.length],
            offsetHeight: 30
          })
        }
        this.sineCosine(0, 0, 0)
        this.positionAll()
        this.setTimer()
      } else {
        this.getListStyle()
      }
    },
    update() {
      // 三角函数只是已完全忘记...（大概是在有限范围内取一个立体空间点，并基于z轴做一个透明效果）
      var a = (Math.min(Math.max(-this.option.mouseY, -this.option.size), this.option.size) / this.option.radius) * this.option.tspeed
      var b = (-Math.min(Math.max(-this.option.mouseX, -this.option.size), this.option.size) / this.option.radius) * this.option.tspeed
      const c = 0

      this.option.lasta = a
      this.option.lastb = b

      if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01) {
        return
      }
      this.sineCosine(a, b, c)
      for (let i = 0, len = this.mcList.length; i < len; i++) {
        if (this.overIndex === -1 || this.overIndex !== i) {
          const rx1 = this.mcList[i].cx
          const ry1 = this.mcList[i].cy * this.triFunc.ca + this.mcList[i].cz * -this.triFunc.sa
          const rz1 = this.mcList[i].cy * this.triFunc.sa + this.mcList[i].cz * this.triFunc.ca
          const rx2 = rx1 * this.triFunc.cb + rz1 * this.triFunc.sb
          const ry2 = ry1
          const rz2 = rx1 * -this.triFunc.sb + rz1 * this.triFunc.cb
          const rx3 = rx2 * this.triFunc.cc + ry2 * -this.triFunc.sc
          const ry3 = rx2 * this.triFunc.sc + ry2 * this.triFunc.cc
          const rz3 = rz2
          this.mcList[i].cx = rx3
          this.mcList[i].cy = ry3
          this.mcList[i].cz = rz3
          const per = this.option.d / (this.option.d + rz3)
          this.mcList[i].x = this.option.howElliptical * rx3 * per - this.option.howElliptical * 2
          this.mcList[i].y = ry3 * per
          this.mcList[i].scale = per
          let alpha = per
          alpha = (alpha - 0.6) * (10 / 6)
          this.mcList[i].alpha = alpha * alpha * alpha - 0.2
          this.mcList[i].zIndex = Math.ceil(100 - Math.floor(this.mcList[i].cz))
        }
      }

      this.doPosition()
    },
    positionAll() {
      // 起点的计算
      let phi = 0
      let theta = 0
      const max = this.mcList.length
      for (let i = 0; i < max; i++) {
        if (this.option.distr) {
          phi = Math.acos(-1 + (2 * (i + 1) - 1) / max)
          theta = Math.sqrt(max * Math.PI) * phi
        } else {
          phi = Math.random() * Math.PI
          theta = Math.random() * (2 * Math.PI)
        }
        // 坐标变换
        this.mcList[i].cx = this.option.radius * Math.cos(theta) * Math.sin(phi)
        this.mcList[i].cy = this.option.radius * Math.sin(theta) * Math.sin(phi) * 1.88
        this.mcList[i].cz = this.option.radius * Math.cos(phi)
      }
    },
    doPosition() {
      const si = this.overIndex === -1 ? {} : (this.styleArray[this.overIndex] || {})
      const oh = this.$refs.wordCloud.offsetHeight
      const ow = this.$refs.wordCloud.offsetWidth
      this.styleArray = []

      for (let i = 0, len = this.mcList.length; i < len; i++) {
        if (this.overIndex !== i) {
          const cow = this.$refs.wordCloud.children[i].offsetWidth
          const coh = this.$refs.wordCloud.children[i].offsetHeight
          var left = this.mcList[i].cx + ow / 2 - cow / 2

          // 设置字体左右方向在指定显示区域内
          if ((left + cow) > ow) {
            left = ow - cow
          } else if ((left + cow) < cow) {
            left = 0
          }

          this.styleArray.push({
            fontSize: (this.clouds[i]['size'] || this.wordMinSize + (i % 10)) + 'px', // 用户自定义字体大小 或 系统随机字体大小
            filter: 'alpha(opacity=' + 100 * this.mcList[i].alpha + ')',
            top: this.mcList[i].cy + oh / 2 - coh / 2 + 'px',
            fontWeight: i % 2 === 1 ? 'normal' : 'bold',
            zIndex: this.mcList[i].zIndex,
            opacity: this.mcList[i].alpha,
            color: this.mcList[i].color,
            left: left + 'px'
          })

          // 读取用户自定义字体加粗状态
          if (this.clouds[i].weight !== undefined) {
            if (this.clouds[i].weight) {
              this.styleArray[i]['fontWeight'] = 'bold'
            } else {
              this.styleArray[i]['fontWeight'] = 'normal'
            }
          }

          // 读取用户自定义字体颜色
          if (this.clouds[i].color) {
            this.styleArray[i]['color'] = this.clouds[i]['color']
          }
        } else {
          // 鼠标悬停云标签不做位置更新操作
          this.styleArray.push(si)
        }
      }
    },
    sineCosine(a, b, c) {
      const PI = Math.PI / 180
      this.triFunc.sa = Math.sin(a * PI)
      this.triFunc.ca = Math.cos(a * PI)
      this.triFunc.sb = Math.sin(b * PI)
      this.triFunc.cb = Math.cos(b * PI)
      this.triFunc.sc = Math.sin(c * PI)
      this.triFunc.cc = Math.cos(c * PI)
    },
    getBoxStyle() {
      var ww = this.width
      var hh = this.height

      if (typeof ww === 'number' && !isNaN(ww)) {
        if (ww <= 0 || (ww > 1 && ww < 100)) {
          ww = 300 + 'px'
        } else if (ww > 0 && ww <= 1) {
          ww = ww * 100 + '%'
        } else {
          ww += 'px'
        }
      } else {
        ww = 300 + 'px'
      }

      if (typeof hh === 'number' && !isNaN(hh)) {
        if (hh < 100) {
          hh = 360 + 'px'
        } else {
          hh = (hh + 60) + 'px'
        }
      } else {
        hh = 360 + 'px'
      }

      return 'width: ' + ww + '; height: ' + hh + '; ' + this.myStyle
    },
    getListStyle() {
      const rr = this.rows
      const id = this.type
      const rw = this.$data.rowWidth
      const ww = this.$refs['word' + id].offsetWidth
      // const ww = document.getElementById(id).offsetWidth
      if (typeof rr === 'number' && !isNaN(rr)) {
        if (ww <= rw || rr < 2) {
          this.$data.liStyle = 'width: 100%;'
        } else if ((ww / rw) >= rr) {
          this.$data.liStyle = 'width: ' + (1 / Math.floor(rr) * 100) + '%;'
        } else {
          this.$data.liStyle = 'width: ' + (1 / Math.ceil(ww / rw) * 100) + '%;'
        }
      } else {
        this.$data.liStyle = 'width: 100%;'
      }
    }
  }
}
</script>

<style>
.word-cloud p {
    top: 0px;
    left: 0px;
    width: auto;
    padding: 2px;
    cursor: pointer;
    position: absolute;
    text-align: center;
    white-space: nowrap;
    display: inline-block;
    margin: 0 10px 15px 0;
}
.word-dot-list,
.word-num-list {
  padding: 8px;
  overflow: hidden;
}
.word-dot-list li,
.word-num-list li {
  text-overflow: ellipsis;
  display: inline-block;
  padding-right: 10px;
  white-space: nowrap;
  line-height: 30px;
  text-align: left;
  overflow: hidden;
  font-size: 15px;
  cursor: pointer;
  height: 30px;
}
</style>
